/**
 iTextUploader
 (C) 2008 MyselftDev (myselftdev@gmail.com)
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; version 2
 of the License.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/* Revision: $Id: ITUImport.m 61 2009-01-30 15:07:16Z myselftdev $ */

#import "ITUImport.h"

@implementation ITUImport

@synthesize delegate;
@synthesize username;
@synthesize password;
@synthesize lastId;
@synthesize uploadProgressDelegate;

@synthesize returnData;

#pragma mark Alloc/Dealloc

- (id)init
{
	self = [super init];
	
	if(self)
	{
		username = nil;
		password = nil;
		delegate = nil;
		uploadProgressDelegate = nil;
		lastId = 0;
		self.returnData = nil;
		ABArray = nil;
		
		queue = [[NSOperationQueue alloc] init];
	}
	
	return self;
}

- (void) dealloc
{
	[delegate release];
	[password release];
	[username release];
	
	[queue release];
	
	[super dealloc];
}

#pragma mark Start the request

- (void) startRequest
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	if([delegate respondsToSelector:@selector(setStatus:)])
	{
		/*[NSThread detachNewThreadSelector:@selector(setStatus:)
								 toTarget:delegate
							   withObject:[NSNumber numberWithInt:kImportStatusStarting] ];
		[NSThread sleepForTimeInterval:0.2];*/
		[delegate performSelectorOnMainThread:@selector(setStatus:) withObject:[NSNumber numberWithInt:kImportStatusStarting] waitUntilDone:YES];
	}
	
	NSLog(@"Starting upload");
	
	ABArray = nil;
	self.returnData = nil;
	
	[self loadAddressBook];
	
	[pool release];
}

# pragma mark Internal methods

// Download Address Book data
- (void) loadAddressBook
{
	NSLog(@"Requesting Address Book");
	if([delegate respondsToSelector:@selector(setStatus:)])
	{
		/*[NSThread detachNewThreadSelector:@selector(setStatus:)
								 toTarget:delegate
							   withObject:[NSNumber numberWithInt:kImportStatusLoadingAddressBook] ];
		[NSThread sleepForTimeInterval:0.2];*/
		[delegate performSelectorOnMainThread:@selector(setStatus:) withObject:[NSNumber numberWithInt:kImportStatusLoadingAddressBook] waitUntilDone:YES];
	}
	
	NSURL *ABRequestURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", @BASEURL, [@"remoteclient.php?mode=addressbook&appversion=" stringByAppendingString:@APPVERSION]]];
	ASIFormDataRequest *ABRequest = [[[ASIFormDataRequest alloc] initWithURL:ABRequestURL] autorelease];
	[ABRequest setDelegate:self];
	[ABRequest setDidFinishSelector:@selector(addressBookLoaded:)];
	[ABRequest setDidFailSelector:@selector(connectionError:)];
	[ABRequest setPostValue:[username stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"username"];
	[ABRequest setPostValue:[password stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"password"];
	[queue addOperation:ABRequest];
}

- (void) addressBookLoaded: (ASIHTTPRequest *)theRequest
{
	NSLog(@"Received Address Book data");
	if([delegate respondsToSelector:@selector(setStatus:)])
	{
		/*[NSThread detachNewThreadSelector:@selector(setStatus:)
								 toTarget:delegate
							   withObject:[NSNumber numberWithInt:kImportStatusReceivedAddressBook] ];
		[NSThread sleepForTimeInterval:0.2];*/
		[delegate performSelectorOnMainThread:@selector(setStatus:) withObject:[NSNumber numberWithInt:kImportStatusReceivedAddressBook] waitUntilDone:YES];
	}
	
	NSError *requestError = [theRequest error];
	NSString *ABString = nil;
	if(requestError)
	{
		[self throwError:[NSString stringWithFormat:@"Address Book download error: %@", [requestError localizedDescription]]];
		return;
	}
	
	ABString = [theRequest dataString];
	
	//NSLog(@"%@", ABString);
	
	NSString *contentType = [[theRequest responseHeaders] objectForKey:@"Content-Type"];
	if(![contentType isEqualToString:@"application/json"] || ![ABString length])
	{
		NSLog(@"Wrong content type: %@", contentType);
		[self throwError:@"Address Book download error"];
		return;
	}
	
	SBJSON *JSON = [SBJSON new];
	JSON.maxDepth = 3;
	ABArray = [JSON objectWithString:ABString allowScalar:NO error:nil];
	[JSON release];
	if(ABArray == nil || ![ABArray isKindOfClass:[NSArray class]])
	{
		NSLog(@"Response is not JSON");
		[self throwError:@"Address Book download error"];
		return;
	}
	
	[self prepareData];
}

- (void) prepareData
{
	NSAutoreleasePool *innerPool = [ [NSAutoreleasePool alloc] init];
	
	NSLog(@"Preparing data");
	
	if([delegate respondsToSelector:@selector(setStatus:)])
	{
		/*[NSThread detachNewThreadSelector:@selector(setStatus:)
								 toTarget:delegate
							   withObject:[NSNumber numberWithInt:kImportStatusPreparingData] ];
		[NSThread sleepForTimeInterval:0.2];*/
		[delegate performSelectorOnMainThread:@selector(setStatus:) withObject:[NSNumber numberWithInt:kImportStatusPreparingData] waitUntilDone:YES];
	}
	
	//NSLog(@"%@", ABArray);
	
	sqlite3 *db;
	NSInteger errcode;
	
#if (TARGET_IPHONE_SIMULATOR)
	errcode = sqlite3_open([[[NSBundle mainBundle] pathForResource:@"sms" ofType:@"db"] UTF8String], &db);
#else
	errcode = sqlite3_open([[@UPLOAD_FILE stringByExpandingTildeInPath] UTF8String], &db);
#endif
	
	if(errcode)
	{
		[innerPool release];
		[self throwError:@"SQLiteError - Cannot open SMS database"];
		return;
	}
	
	sqlite3_stmt *query = nil;
	const char *sql = [ [NSString stringWithFormat:@"SELECT ROWID, address, date, text, flags FROM message WHERE ROWID > %u", lastId] UTF8String];
	if(sqlite3_prepare_v2(db, sql, -1, &query, NULL) != SQLITE_OK)
	{
		NSLog(@"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(db));
		sqlite3_close(db);
		[innerPool release];
		[self throwError:@"Cannot prepare message query"];
		return;
	}
	
	ABAdd = [[NSMutableArray alloc] init];
	
	NSUInteger ROWID;
	const void *address;
	NSUInteger addressId;
	NSInteger date;
	const void *text;
	NSInteger flags;
	
	NSString *rowData = nil;
	
	NSMutableData *POSTData = [[NSMutableData alloc] init];
	[POSTData appendData:[@"[" dataUsingEncoding:NSUTF8StringEncoding]];
	
	BOOL first = YES;
	lastROWID = 0;
	
	while(sqlite3_step(query) == SQLITE_ROW)
	{
		ROWID = (NSUInteger) sqlite3_column_int(query, 0);
		address = sqlite3_column_text(query, 1);
		date = (NSInteger) sqlite3_column_int(query, 2);
		text = sqlite3_column_text(query, 3);
		flags = (NSInteger) sqlite3_column_int(query, 4);
		
		addressId = [self findNumber:[NSString stringWithUTF8String:address] inAddressBook:ABArray];
		
		// a = address, d = date, t = text, f = flags
		rowData = [NSString stringWithFormat:@"%s{\"id\":%u,\"a\":\"%u\",\"d\":%d,\"t\":\"%@\",\"f\":%d}",
				   (first ? "" : ","),
				   ROWID,
				   addressId,
				   date,
				   [self escapeString:[NSString stringWithUTF8String:text]],
				   flags];
		[POSTData appendData:[rowData dataUsingEncoding:NSUTF8StringEncoding]];
		
		[innerPool release];
		innerPool = [ [NSAutoreleasePool alloc] init];
		
		first = NO;
		if(ROWID > lastROWID)
		{
			lastROWID = ROWID;
		}
	}
	[POSTData appendData:[@"]" dataUsingEncoding:NSUTF8StringEncoding]];
	
	NSLog(@"lastROWID: %u", lastROWID);
	lastId = lastROWID;
	
	// Don't need an inner autorelease pool anymore, here
	[innerPool release];
	
	/*NSString *printString = [[NSString alloc] initWithData:POSTData encoding:NSUTF8StringEncoding];
	 NSLog(@"%@", printString);
	 [printString release];*/
	
	sqlite3_finalize(query);
	
	query = nil;
	NSUInteger minDate = 0;
	NSUInteger maxDate = 0;
	if(sqlite3_prepare_v2(db, "SELECT MIN(date), MAX(date) FROM message", -1, &query, NULL) != SQLITE_OK)
	{
		NSLog(@"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(db));
		sqlite3_close(db);
		[self throwError:@"Cannot prepare min and max date query"];
		return;
	}
	if(sqlite3_step(query) == SQLITE_ROW)
	{
		minDate = (NSUInteger) sqlite3_column_int(query, 0);
		maxDate = (NSUInteger) sqlite3_column_int(query, 1);
		//NSLog(@"Min date: %d, Max date: %d", minDate, maxDate);
	}
	else
	{
		[self throwError:@"Cannot retrive min and max date"];
		sqlite3_finalize(query);
		sqlite3_close(db);
		return;
	}
	sqlite3_finalize(query);
	
	sqlite3_close(db);
	
	[self uploadData:(NSData*)POSTData minDate:minDate maxDate:maxDate];
	
	[POSTData release];
}

- (void) uploadData: (NSData*)data minDate:(NSUInteger)minDate maxDate:(NSUInteger)maxDate
{
	[data retain];
	
	NSLog(@"Starting upload");
	if([delegate respondsToSelector:@selector(setStatus:)])
	{
		/*[NSThread detachNewThreadSelector:@selector(setStatus:)
								 toTarget:delegate
							   withObject:[NSNumber numberWithInt:kImportStatusUploading] ];
		[NSThread sleepForTimeInterval:0.2];*/
		[delegate performSelectorOnMainThread:@selector(setStatus:) withObject:[NSNumber numberWithInt:kImportStatusUploading] waitUntilDone:YES];
	}
	
	NSURL *theURL = [NSURL URLWithString:[@BASEURL stringByAppendingString:@"remoteclient.php"]];
	ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:theURL] autorelease];
	
	[request setDelegate:self];
	[request setDidFinishSelector:@selector(uploadDone:)];
	[request setDidFailSelector:@selector(connectionError:)];
	[request setUploadProgressDelegate:uploadProgressDelegate];
	[request setTimeOutSeconds:120];
	
	[request setPostValue:username forKey:@"username"];
	[request setPostValue:password forKey:@"password"];
	[request setPostValue:@"import" forKey:@"mode"];
	[request setPostValue:@APPVERSION forKey:@"appversion"];
	[request setPostValue:[NSNumber numberWithUnsignedInt:minDate] forKey:@"mindate"];
	[request setPostValue:[NSNumber numberWithUnsignedInt:maxDate] forKey:@"maxdate"];
	[request setData:data forKey:@"data"];
	
	[data release];
	
	// New contacts
	if([ABAdd count])
	{
		SBJSON *JSON = [SBJSON new];
		JSON.maxDepth = 3;
		
		NSData *ABAddData = [[JSON stringWithObject:ABAdd allowScalar:NO error:nil] dataUsingEncoding:NSUTF8StringEncoding];
		[JSON release];
		[request setData:ABAddData forKey:@"addressbook"];
		
		[ABAdd release];
		
		//NSLog(@"%@", [JSON stringWithObject:ABAdd allowScalar:NO error:nil]);
	}
	
	[queue addOperation:request];
}

- (void) uploadDone: (ASIHTTPRequest*) theRequest
{
	NSString *response = [theRequest dataString];
	NSLog(@"Response: %@", response);
	
	@try
	{
		if([response length] == 0)
		{
			NSException *e = [NSException exceptionWithName:nil reason:nil userInfo:nil];
			@throw e;
		}
		
		self.returnData = [response propertyList];
		
		if(![returnData isKindOfClass:[NSDictionary class]])
		{
			NSException *e = [NSException exceptionWithName:nil reason:nil userInfo:nil];
			@throw e;
		}
		
		if([delegate respondsToSelector:@selector(setStatus:)])
		{
			/*[NSThread detachNewThreadSelector:@selector(setStatus:)
									 toTarget:delegate
								   withObject:[NSNumber numberWithInt:kImportComplete] ];*/
			[delegate performSelectorOnMainThread:@selector(setStatus:) withObject:[NSNumber numberWithInt:kImportComplete] waitUntilDone:YES];
		}
	}
	@catch(NSException *e)
	{
		[self throwError:@"Response error"];
		return;
	}
}

#pragma mark Error handling

- (void) connectionError: (ASIHTTPRequest *)request
{
	NSError *requestError = [request error];
	[self throwError:[NSString stringWithFormat:@"Conection error: %@", [requestError localizedDescription]]];
}

- (void) throwError: (NSString *)error
{
	NSLog(@"Error happened: %@", error);
	
	NSArray *keys = [NSArray arrayWithObjects:@"error", @"message", nil];
	NSArray *values = [NSArray arrayWithObjects:[NSNumber numberWithBool:YES], error, nil];
	self.returnData = [NSDictionary dictionaryWithObjects:values forKeys:keys];
	
	if([delegate respondsToSelector:@selector(setStatus:)])
	{
		/*[NSThread detachNewThreadSelector:@selector(setStatus:)
								 toTarget:delegate
							   withObject:[NSNumber numberWithInt:kImportError] ];*/
		[delegate performSelectorOnMainThread:@selector(setStatus:) withObject:[NSNumber numberWithInt:kImportError] waitUntilDone:YES];
	}
}

#pragma mark Utilities
- (NSUInteger) findNumber: (NSString*)number inAddressBook: (NSMutableArray *)array
{
	//NSLog(@"Searching number: %@", number);
	
	// First of all, check inside the WebApp address book
	NSUInteger equal;
	NSUInteger maxId = 0;
	
	NSAutoreleasePool *innerPool = [ [NSAutoreleasePool alloc] init];
	
	for(NSDictionary *element in array)
	{
		for(NSString *addr in [element objectForKey:@"a"])
		{
			equal = CPPhoneNumbersEqualStrict([number UTF8String], [addr UTF8String]);
			//NSLog(@"%@ %@ %@ %u", number, addr, CPPhoneNumberCopyActiveCountryCode(), equal);
			
			if(equal == 1)
			{
				[innerPool release];
				return [ [element objectForKey:@"i"] unsignedIntValue];
			}
			
			if([ [element objectForKey:@"i"] unsignedIntValue] > maxId)
			{
				maxId = [ [element objectForKey:@"i"] unsignedIntValue];
			}
		}
	}
	
	// Free some memory
	[innerPool release];
	innerPool = [ [NSAutoreleasePool alloc] init];
	
	//NSLog(@"Max id: %u", maxId);
	//NSLog(@"Not found in already imported. Looking at the phone address book");
	
	// Not already imported? Let's look at the phone address book!
	ABAddressBookRef PhoneAB = ABAddressBookCreate();
	
	CFArrayRef allPeople  = ABAddressBookCopyArrayOfAllPeople(PhoneAB);
	
	CFIndex size = ABAddressBookGetPersonCount(PhoneAB);
	ABRecordRef someone;
	ABMutableMultiValueRef phoneNumbersMultiValue;
	CFArrayRef phoneNumbers;
	CFStringRef contactName;
	NSDictionary *addElement;
	NSString *addNumber;
	NSUInteger i;
	for (i = 0; i < size; i++)
	{
		someone = CFArrayGetValueAtIndex(allPeople, i);
		phoneNumbersMultiValue = ABRecordCopyValue(someone, kABPersonPhoneProperty);
		phoneNumbers = ABMultiValueCopyArrayOfAllValues(phoneNumbersMultiValue);
		
		//NSLog(@"%@", phoneNumbers);
		for(NSString* elem in (NSArray*) phoneNumbers)
		{
			equal = CPPhoneNumbersEqualStrict([number UTF8String], [elem UTF8String]);
			if(equal == 1)
			{
				contactName = ABRecordCopyCompositeName(someone);
				
				const char *phoneNumberNormalized = CPPhoneNumberCopyNormalized([elem UTF8String]);
				addNumber = [NSString stringWithFormat:@"%s", phoneNumberNormalized];
				addElement = [NSDictionary dictionaryWithObjectsAndKeys:
							  [NSArray arrayWithObject:addNumber], @"a",
							  [NSNumber numberWithUnsignedInt:(maxId + 1)], @"i",
							  contactName, @"n",
							  nil];
				//NSLog(@"%s", CPPhoneNumberCopyNormalized([elem UTF8String]));
				//NSLog(@"%@", addElement);
				
				//NSLog(@"Found in: %@", addElement);
				
				[array addObject:addElement];
				[ABAdd addObject:addElement];
				
				free(phoneNumberNormalized);
				
				CFRelease(contactName);
				CFRelease(phoneNumbersMultiValue);
				CFRelease(phoneNumbers);
				CFRelease(PhoneAB);
				CFRelease(allPeople);
				
				[innerPool release];
				
				return (maxId + 1);
			}
		}
		
		CFRelease(phoneNumbersMultiValue);
		CFRelease(phoneNumbers);
	}
	
	CFRelease(allPeople);
	CFRelease(PhoneAB);
	
	// Not even in the phone address book? We should add it as it is...
	addElement = [NSDictionary dictionaryWithObjectsAndKeys:
				  [NSArray arrayWithObject:number], @"a",
				  [NSNumber numberWithUnsignedInt:(maxId + 1)], @"i",
				  number, @"n",
				  nil];
	
	[array addObject:addElement];
	[ABAdd addObject:addElement];
	
	[innerPool release];
	
	return (maxId + 1);
}

- (NSString *) escapeString:(NSString*)theString
{
	[theString retain];
	NSMutableString *result = [[[NSMutableString alloc] initWithCapacity:[theString length]] autorelease];
	
	unichar c;
	NSUInteger i;
	for(i = 0; i < [theString length]; i++)
	{
		c = [theString characterAtIndex:i];
		
		switch (c) {
			case '\\':
				[result appendString:@"\\\\"];
				break;
			case '"':
				[result appendString:@"\\\""];
				break;
			case '\t':
				[result appendString:@"\\t"];
				break;
			case '\n':
				[result appendString:@"\\n"];
				break;
			case '\r':
				[result appendString:@"\\r"];
				break;
			case '\b':
				[result appendString:@"\\b"];
				break;
			case '\f':
				[result appendString:@"\\f"];
				break;
			default:
				if(c < 0x20)
				{
					[result appendFormat:@"\\u%04x", c];
				}
				else
				{
					[result appendFormat:@"%C", c];
				}
				break;
		}
	}
	
	[theString release];
	
	return (NSString *) result;
}
@end
